/* Trabajo Practico 4 (Segunda Entrega)
# Cliente
#
# - Integrantes:
#	- Martinez, Guillermo		DNI: 34510461
#	- Rodriguez, Lucas			DNI: 34265017
*/

#include "include/defines.h"
#include "include/sdl/pantalla.h"
#include "include/sockets/sockets_c.h"
#include "Mensajes.h"
#include "DatosJuego.h"

using namespace std;

#define TAM_MSJ 4096					// Tamaño máximo parámetro entrada/salida
#define PUERTO 23457					// Numero puerto arbitrario
#define HOST "127.0.0.1"

// Variables globales
t_scr principal;
s_sockets socks;						// Descriptores que entrega server maestro
s_mov mov;								// Variable registra movimientos
char nombre1[20];						// Nombre Jugador
char nombre2[20];						// Nombre Contrincante
int ronda = 1;
bool imprimir;
int terminacion = 0;					// Flag para terminar threads
int socket_op;							// Descriptor a operar

// Threads IDs
pthread_t tid_socket;
pthread_t tid_pantalla;
pthread_t tid_lectura;
pthread_t actualizar;

// Semáforos movimientos
sem_t nuevo_mov;
sem_t sem_mov;
sem_t mov_valido;

// Semáforos threads
sem_t mutex_pantalla;
sem_t mutex_periferico;
sem_t mutex_socket;
sem_t socket_disp;
sem_t cliente_conectado;
sem_t flag_global;

// Mensaje de inicio
Mensaje msj_inicio;

// Mútex para imprimir en pantalla
pthread_mutex_t imprimir_en_pantalla = PTHREAD_MUTEX_INITIALIZER;

// Funciones secundarias
void actualizar_matriz(int);
void iniciar_semaforos();
void borrar_semaforos();
void iniciar_vars();
void ImprimirTablero(short int [][3]);
void buscar_coordenadas(int,int*,int*);
void cambia_partida();

// Handler que finaliza cliente
void finalizar(int);

// Función error
void error(char*);

//////////////////

int main(int argc, char *argv[])
{
	int pos_ant=-1;
	
	// Manejar la finalizacion
	signal(SIGUSR1,finalizar);
	signal(SIGINT, finalizar);
	signal(SIGTERM,finalizar);

	// Enviamos el msj al PUERTO del servidor en la máquina HOST
	cout << "Ingrese su nombre:" << endl;
	fgets(nombre1,sizeof(nombre1),stdin);
	strcpy(msj_inicio.Nombre,nombre1);
	msj_inicio.Tipo=InicioConexion;

	if(!XInitThreads())
		perror("Failed to initialize Xlib muti-threading support");

	// Iniciar semáforos
	iniciar_semaforos();

	// Iniciar thread socket cliente
	pthread_create(&tid_socket, NULL, thread_socket_c, (void*) &mov);
	sem_wait(&cliente_conectado);

	// Iniciar SDL
	start_sdl();

	// Inicializo variables del cliente
	iniciar_vars();

	// Iniciar threads de pantalla y de lectura
	pthread_create(&tid_pantalla, NULL, thread_pantalla, (void*) &mov);
	pthread_create(&tid_lectura,  NULL, thread_lectura,  (void*) &mov);

	// Ciclo principal
	sleep(1);

	// Desbloqueo socket para enviar y recibir nombres
	unlock_socket(INICIO);
		
	// Espero mensaje del socket
	sem_wait(&nuevo_mov);

	while(1)
	{		
		// Recibo nombre de oponente
		pthread_mutex_lock(&imprimir_en_pantalla);
		strcpy(nombre2,msj_inicio.Nombre);
		ronda = msj_inicio.Ronda;
		printf("Juego contra %s la ronda %d\n",nombre2, ronda);
		imprimir = true;
		pthread_mutex_unlock(&imprimir_en_pantalla);
		mov.estado = NO_TERMINO;

		// Desbloqueo pantalla para que refresque
		sem_wait(&sem_mov);
		unlock_pantalla();
			
		// Mientras no termine la ronda
		while(mov.estado != GANADOR_RONDA && mov.estado != PERDEDOR_RONDA && mov.estado != CAMPEON)
		{
			// Desbloqueo socket para esperar turno
			unlock_socket(RECV);
			
			// Espero que me sea mi turno
			sem_wait(&nuevo_mov);
			
			printf("Proceso %d:%d:%d:%d\n",mov.accion,mov.estado,mov.posicion,mov.n_posicion);
			
			// Comprobar estado partida
			if(mov.estado != NO_TERMINO)
				cambia_partida();
			
			else
			{
				// Si hubo movimiento del otro jugador
				if(mov.posicion != 0)
				{
					sem_wait(&sem_mov);
					printf("Movimiento anterior: %d -> %d\n",mov.posicion,mov.n_posicion);
					actualizar_matriz(1);
					unlock_pantalla();
				}
					
				// Selección y desplazamiento ficha
				if(mov.accion == MOVER)
				{
					printf("Selecciono 1era posicion\n");
					
					do
					{
						do
						{				
							// Desbloqueo teclado para que lea
							unlock_periferico();
						
							// Espero que me de un mensaje
							sem_wait(&nuevo_mov);
							
							// Desbloqueo a socket para que envíe
							printf("Socket tiene que enviar movimiento: %d -> %d\n",mov.posicion,mov.n_posicion);
							unlock_socket(SEND);
							
							// Espero que me entregue un mensaje
							sem_wait(&nuevo_mov);
						}while(mov.accion != VALIDO);
						
						printf("Selecciono 2da posicion\n");
						pos_ant = mov.posicion;
					
						// Desbloqueo teclado para que lea
						unlock_periferico();
					
						// Espero que me de un mensaje
						sem_wait(&nuevo_mov);
						
						mov.n_posicion = mov.posicion;
						mov.posicion = pos_ant;
						
						// Desbloqueo a socket para que envíe
						printf("Socket tiene que enviar movimiento: %d -> %d\n",mov.posicion,mov.n_posicion);
						unlock_socket(SEND);
						
						// Espero que me entregue un mensaje
						sem_wait(&nuevo_mov);
					}while(mov.accion != MOVER_CONF || mov.accion == VALIDO);
					
					mov.posicion = pos_ant;
					printf("Se ingresó un movimiento válido: %d -> %d\n",mov.posicion,mov.n_posicion);
				}
				
				// Colocación ficha
				else
				{
					printf("Colocación\n");
					
					do
					{			
						// Desbloqueo teclado para que lea
						unlock_periferico();
					
						// Espero que me de un mensaje
						sem_wait(&nuevo_mov);
						printf("Se ingresó un movimiento, tengo que enviarlo\n");
						
						// Desbloqueo a socket para que envíe
						unlock_socket(SEND);
						
						// Espero que me entregue un mensaje
						sem_wait(&nuevo_mov);
					}while(mov.accion == INVALIDO);
					
					printf("Se ingresó un movimiento válido\n");
				}
				
				// Uso dato para actualizar matriz
				actualizar_matriz(0);
				ImprimirTablero(principal.matriz);
				
				// Desbloqueo pantalla para que refresque
				sem_wait(&sem_mov);
				unlock_pantalla();
				
				sleep(1);
			}
		}
		
		// Comprobar estado partida
		if(mov.estado != NO_TERMINO)
			cambia_partida();
		
		// Tengo que jugar contra otro jugador, recibo el nombre
		unlock_socket(NUEVO_CONTRINCANTE);
		
		// Espero mensaje del socket
		sem_wait(&nuevo_mov);
		
		if(mov.estado == CAMPEON)
			cambia_partida();
	}
	
	// Espero a que terminen
	pthread_join(tid_socket, NULL);
	pthread_join(tid_pantalla, NULL);
	pthread_join(tid_lectura, NULL);
    
	return 1;
}

void finalizar(int sig)
{
	// Espero a que terminen
	pthread_cancel(tid_socket);
	pthread_join(tid_socket, NULL);

	pthread_cancel(tid_pantalla);
	pthread_join(tid_pantalla, NULL);

	pthread_cancel(tid_lectura);
	pthread_join(tid_lectura, NULL);

	pthread_cancel(actualizar);
	pthread_join(actualizar, NULL);

	close(socket_op);
	finalizar_sdl();
	printf("\nCliente finalizado por el usuario\n");
	exit(0);
}

void actualizar_matriz(int val)
{
	int posc=0,posf=0,n_posc=0,n_posf=0,aux=-1;
	
	// Transformar coordenadas
	buscar_coordenadas(mov.posicion,&posf,&posc);
	
	// Movimiento nuestro o de rival? val
	// Hubo desplazamiento o colocación?
	if(mov.accion == 2 || mov.n_posicion != 0)
	{
		// Transformar coordenadas
		buscar_coordenadas(mov.n_posicion,&n_posf,&n_posc);
		principal.matriz[posf][posc] = 0;
		posc = n_posc;
		posf = n_posf;
	}
	
	if(val == 0)
		principal.matriz[posf][posc] = ESFERA;
	else
		principal.matriz[posf][posc] = CUBO;
}

void iniciar_semaforos()
{
	// Semáforos
	sem_init(&nuevo_mov,4,0);						// Mutex para inicio partida
	sem_init(&sem_mov,4,1);							// Mutex para mov

	// Semáforos sincronización threads
	sem_init(&mutex_pantalla,3,0);
	sem_init(&mutex_periferico,3,0);
	sem_init(&mutex_socket,3,0);
	sem_init(&socket_disp,3,0);
	sem_init(&flag_global,5,1);
}

void borrar_semaforos()
{
	// Semáforos
	sem_destroy(&nuevo_mov);	
	sem_destroy(&sem_mov);
	
	// Semáforos sincronización threads
	sem_destroy(&mutex_pantalla);
	sem_destroy(&mutex_periferico);
	sem_destroy(&mutex_socket);
	sem_destroy(&socket_disp);
	sem_destroy(&flag_global);
}

void iniciar_vars()
{
	int i,j;
		
	for(i=0;i<3;i++)
		for(j=0;j<3;j++)
			principal.matriz[i][j] = 0;
			
	mov.posicion=mov.n_posicion=mov.accion=0;
}

void ImprimirTablero(short int mat[][3])
{
	int f,c;
	
	for(f=0;f<3;f++)
	{
		for(c=0;c<3;c++)
			printf("| %d |",mat[f][c]);
					
		printf("\n");
	}
}

void buscar_coordenadas(int posicion, int *fDestino, int *cDestino)
{
	// Convierte número de grilla en posición F y C

	int f,c,pos=1;
	
	for(f=0;f<3;f++)
		for(c=0;c<3;c++)
		{
			if(pos == posicion)
			{
				*fDestino = f;
				*cDestino = c;
				return;
			}
			pos++;
		}
}

void cambia_partida()
{
	printf("Hubo cambio en partida\n");
	//unlock_pantalla();
	sleep(1);
		
	// Si el evento implica terminar
	if(mov.estado == PERDEDOR_RONDA || mov.estado == CAMPEON)
	{
		printf("\nTerminando cliente...");
		
		// Flag para terminar threads
		sem_wait(&flag_global);
		terminacion = 1;
		sem_post(&flag_global);
		
		// Unlocks necesarios
		unlock_pantalla();
		sleep(1);
		unlock_socket(0);
		unlock_periferico();
		
		finalizar_sdl();
			
		// Join de los threads
      pthread_cancel(tid_lectura);
		pthread_join(tid_lectura,NULL);
	
      pthread_cancel(tid_pantalla);
		pthread_join(tid_pantalla,NULL);

      pthread_cancel(tid_socket);
		pthread_join(tid_socket,NULL);
			
		pthread_cancel(actualizar);
		pthread_join(actualizar,NULL);
		
		borrar_semaforos();
		printf(" OK\n");
		exit(0);
	}
	
	// Reinicializo variables		
	iniciar_vars();
	
	// Refresco pantalla
	sem_wait(&sem_mov);
	unlock_pantalla();
}

// Funcion error
void error(char *e)
{
	printf("%s\n",e);
	finalizar_sdl();
	exit(1);
}
